//
//  commo.c
//  myvpn
//
//  Created by Zhuobin He on 12-12-31.
//  Copyright (c) 2012年 Zhuobin He. All rights reserved.
//

#include "SysLib.h"

#include "Common.h"

bool check_str(const char *str) {
    return str && strlen(str);
}


char * trim(char *str) {
    {
        char *i1 = str + strspn(str, WHITESPACE);
        if (i1 != str) {
            memmove(str, i1, sizeof(char) * (strlen(i1) + 1));
        }
    }
    int i;
    for (i=(int)strlen(str)-1; i>=0; i--) {
        if (strchr(WHITESPACE, str[i]) == NULL) break;
        str[i] = '\0';
    }
    return str;
}

void zmalloc(void *dst, size_t size) {
    void **tmp = (void **)dst;
    (*tmp) = malloc(size);
    bzero(*tmp, size);
}



int execute_cmd(const char *cmd, char *result, int limit) {
    FILE *ptr;
    char buffer[1030];
    if((ptr=popen(cmd, "r"))!=NULL) {
        while(fgets(buffer, 1024, ptr)!=NULL) {
            int nread = (int)strlen(buffer);
            if (limit > 1) {
                nread = min(nread, limit - 1);
                strncpy(result, buffer, nread);
                result[nread] = '\0';
                result += nread;
                limit -= nread;
            }
        }
        pclose(ptr);
        ptr = NULL;
        return 0;
    }
    else {
        log_error("popen %s error\n", cmd);
        return 1;
    }
}

void route_delete(PRoute route) {
    if (route->address) free(route->address);
    free(route);
}

void vpn_config_delete(PVpnConfig conf) {
    if (conf->users) {
        int i;
        for (i=0; conf->users[i]; i++) {
            user_info_delete(conf->users[i]);
        }
        free(conf->users);
    }

    if (conf->routes) {
        int i;
        for (i=0; conf->routes[i]; i++) {
            route_delete(conf->routes[i]);
        }
        free(conf->routes);
    }

    if (conf->dev) free(conf->dev);
    if (conf->network) free(conf->network);
    if (conf->host) free(conf->host);
    if (conf->log_file) free(conf->log_file);
    if (conf->route_cache_file) free(conf->route_cache_file);
    if (conf->tcp_ports) free(conf->tcp_ports);
    if (conf->udp_ports) free(conf->udp_ports);
    free(conf);
}

bool vpn_config_verify(PVpnConfig conf) {
    if (conf->mode != CLIENT && conf->mode != SERVER) {
        log_error("Only client(0) or server(1) mode is supported");
        return false;
    }
    if (conf->users == NULL || conf->users[0] == NULL) {
        log_error("No user config specified");
        return false;
    }
    if (!check_str(conf->host)) {
        log_error("No host specified");
        return false;
    }
    if (conf->log_level > LOG_TRACE || conf->log_level < LOG_ERROR) {
        log_error("Invalid log_level (1~4)");
        return false;
    }
    if (conf->mode == SERVER && !check_str(conf->network)) {
        log_error("No network specified");
        return false;
    }

    int i;
    for (i=0; conf->users[i]; i++) {
        PUserInfo user = conf->users[i];
        if (!check_str(user->username)) {
            log_error("No username specified for the %d th user", i);
            return false;
        }
        if (!check_str(user->password)) {
            log_error("No password specified for the %d th user", i);
            return false;
        }
    }

    return true;
}

void user_info_delete(PUserInfo obj) {
    if (obj->username) free(obj->username);
    if (obj->password) free(obj->password);
    free(obj);
}


PBuffer buffer_new(int size) {
    PBuffer ret = NULL;
    zmalloc(&ret, sizeof(Buffer));
    ret->buf = (char *) malloc(sizeof(char) * (size + 1));
    ret->capacity = size;
    return ret;
}

void buffer_delete(PBuffer obj) {
    free(obj->buf);
    free(obj);
}

void buffer_copy(PBuffer dst, PBuffer src) {
    if (dst != src) {
        dst->from = 0;
        dst->to = buffer_readable(src);
        memcpy(dst->buf, buffer_pread(src), dst->to);
    } else {
        buffer_compact(src);
    }
}

char * buffer_pread(PBuffer buf) {
    return buf->buf + buf->from;
}

char * buffer_pwrite(PBuffer buf) {
    return buf->buf + buf->to;
}

void buffer_pread_inc(PBuffer buf, int len) {
    buf->from += len;
}

void buffer_pwrite_inc(PBuffer buf, int len) {
    buf->to += len;
}

int buffer_readable(PBuffer buf) {
    return buf->to - buf->from;
}

int buffer_writable(PBuffer buf) {
    return buf->capacity - buf->to;
}

void buffer_compact(PBuffer buf) {
    int size = buffer_readable(buf);
    if (size) {
        if (buf->from) {
            memmove(buf->buf, buf->buf + buf->from, sizeof(char) * size);
            buf->from = 0;
            buf->to = size;
        }
    } else {
        buffer_clear(buf);
    }
}

void buffer_clear(PBuffer buf) {
    buf->from = buf->to = 0;
}

bool buffer_ends_with(PBuffer buf, const char * str) {
    int buf_index = buf->to - 1;
    int i;
    for (i=(int)strlen(str)-1; i>=0; i--,buf_index--) {
        if (buf->buf[buf_index] != str[i]) return false;
    }
    return true;
}

int _log_level = LOG_INFO;

FILE *log_fd = NULL;

void set_log_level(int level) {
    _log_level = level;
}

void set_log_file(const char *log_file) {
    if (log_file == NULL) {
        log_fd = stdout;
    } else {
        log_fd = fopen(log_file, "a");
    }
}

bool is_log_debug() {
    return LOG_DEBUG <= _log_level;
}

void _do_logging(int level, const char *msg, va_list argp) {
    if (level <= _log_level) {
        time_t now = time(NULL);
        char time_str[25];
        strftime(time_str, 24, "%Y-%m-%d %H:%M:%S", localtime(&now));
        char log_content[1024];
        if (strlen(msg) > 800) {
            strncpy(log_content, msg, 800);
            log_content[800] = log_content[801] = log_content[802] = '.';
            log_content[803] = '\0';
        } else {
            vsprintf(log_content, msg, argp);
        }
        switch (level) {
            case LOG_ERROR:
                fprintf(log_fd, "%s %s %s\n", time_str, "[ERROR]", log_content);
                break;
            case LOG_INFO:
                fprintf(log_fd, "%s %s %s\n", time_str, "[INFO]", log_content);
                break;
            case LOG_DEBUG:
                fprintf(log_fd, "%s %s %s\n", time_str, "[DEBUG]", log_content);
                break;
            case LOG_TRACE:
                fprintf(log_fd, "%s %s %s\n", time_str, "[TRACE]", log_content);
                break;
        }
        fflush(log_fd);
    }
}

void log_error(const char *msg, ...) {
    va_list argp;
    va_start(argp, msg);
    _do_logging(LOG_ERROR, msg, argp);
    va_end(argp);
}

void log_info(const char *msg, ...) {
    va_list argp;
    va_start(argp, msg);
    _do_logging(LOG_INFO, msg, argp);
    va_end(argp);
}


#ifdef LOG_DEBUG_ENABLE
void log_debug(const char *msg, ...) {
    va_list argp;
    va_start(argp, msg);
    _do_logging(LOG_DEBUG, msg, argp);
    va_end(argp);
}

void log_trace(const char *msg, ...) {
    va_list argp;
    va_start(argp, msg);
    _do_logging(LOG_TRACE, msg, argp);
    va_end(argp);
}
#endif


char * string_alloc(const char *str) {
    char *ret = (char *) malloc (sizeof(char) * (strlen(str) + 1));
    strcpy(ret, str);
    return ret;
}

int str2int(const char *str) {
    int i;
    sscanf(str, "%d", &i);
    return i;
}

void * thread_runner(void *arg) {
    void **args = (void **) arg;
    PThread thread = (PThread) args[0];
    void *(*target)(void *) = args[1];
    void *inner_args = args[2];
    free(args);
    thread->ret = target(inner_args);
    thread->running = false;
    pthread_exit(NULL);
    return NULL;
}

PThread thread_start(void *(*target)(void *), void *args) {
    PThread thread = NULL;
    zmalloc(&thread, sizeof(Thread));
    thread->running = true;
    void **inner_args = (void **) malloc(sizeof(void*) * 3);
    inner_args[0] = thread;
    inner_args[1] = target;
    inner_args[2] = args;

    pthread_attr_t a;
    pthread_attr_init(&a);
    pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED);
    int status = pthread_create(&thread->thread, &a, thread_runner, inner_args);
    pthread_attr_destroy(&a);
    if (0 == status) {
        return thread;
    }

    log_error("Error starting thread, errno: %d", status);
    free(thread);
    return NULL;
}

void thread_delete(PThread thread) {
    free(thread);
}

PThreadLock threadlock_new() {
    PThreadLock ret = NULL;
    zmalloc(&ret, sizeof(ThreadLock));
    int status = pthread_mutex_init(&ret->mut, NULL);
    if (status == 0) {
        return ret;
    }

    log_error("Error init lock, errno: %d", status);
    free(ret);
    return NULL;
}

bool threadlock_acquire(PThreadLock lock) {
    return 0 == pthread_mutex_lock(&lock->mut);
}

bool threadlock_release(PThreadLock lock) {
    return 0 == pthread_mutex_unlock(&lock->mut);
}

void threadlock_delete(PThreadLock lock) {
    pthread_mutex_destroy(&lock->mut);
    free(lock);
}


PBitSet bitset_new(int size) {
    PBitSet ret = NULL;
    zmalloc(&ret, sizeof(BitSet));
    ret->size = size;
    int bytes = size / 8 + 1;
    zmalloc(&(ret->bits), sizeof(unsigned char) * bytes);
    return ret;
}

bool bitset_check(PBitSet bits, int index) {
    if (index >= bits->size) return false;
    return bits->bits[index / 8] & (1 << (index % 8));
}

void bitset_set(PBitSet bits, int index, bool is_set) {
    if (index >= bits->size) return;
    if (is_set) {
        bits->bits[index / 8] |= 1 << (index % 8);
    } else {
        bits->bits[index / 8] &= ~(1 << (index % 8));
    }
}

void bitset_delete(PBitSet bits) {
    free(bits->bits);
    free(bits);
}



void ip_str_to_num(char *str, int limit, unsigned char *dst) {
    int i;
    char *head = str = string_alloc(str);
    for (i=0; i<limit; i++) {
        char *tmp = strchr(str, '.');
        if (tmp) {
            tmp[0] = '\0';
        }
        dst[i] = (unsigned char)str2int(str);
        if (tmp == NULL) break;
        str = tmp + 1;
    }
    free(head);
}
